package conf

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo"
)

func DefaultConfig() *Config {
	return &Config{
		App:     newDefaultApp(),
		MongoDB: newDefaultMongoDB(),
		Http:    newDefaultHttp(),
		Grpc:    NewDefaultGrpc(),
		Jaeger:  newJaeger(),
	}
}

// 程序的配置对象 config --> object
type Config struct {
	App *App `json:"app" toml:"app"`
	// [mysql]
	MongoDB *mongodb `json:"mysql" toml:"mysql"`
	// [http]
	Http *Http `json:"http" toml:"http"`
	// [grpc]
	Grpc *Grpc `json:"grpc" toml:"grpc"`
	// 程序接入Jaeger配置
	Jaeger *jaeger `toml:"jaeger"`
}

func newDefaultApp() *App {
	return &App{
		Name: "cmdb",
	}
}

type App struct {
	Name string `json:"name" toml:"name"`
}

// 格式化成一个json
func (c *Config) String() string {
	d, _ := json.MarshalIndent(c, "", "  ")
	return string(d)
}

func newDefaultHttp() *Http {
	return &Http{
		Host: "127.0.0.1",
		Port: 8080,
	}
}

type Http struct {
	Host string `json:"host" toml:"host" env:"HTTP_HOST"`
	Port int    `json:"port" toml:"port" env:"HTTP_PORT"`
}

func (h *Http) Address() string {
	return fmt.Sprintf("%s:%d", h.Host, h.Port)
}

func NewDefaultGrpc() *Grpc {
	return &Grpc{
		Host: "127.0.0.1",
		Port: 18080,
	}
}

type Grpc struct {
	Host string `json:"host" toml:"host" env:"GRPC_HOST"`
	Port int    `json:"port" toml:"port" env:"GRPC_PORT"`
}

func (h *Grpc) Address() string {
	return fmt.Sprintf("%s:%d", h.Host, h.Port)
}

func newDefaultMongoDB() *mongodb {
	m := &mongodb{
		UserName:  "cmdb",
		Password:  "123456",
		Database:  "cmdb_mini",
		AuthDB:    "",
		Endpoints: []string{"127.0.0.1:27017"},
	}
	return m
}

type mongodb struct {
	Endpoints []string `toml:"endpoints" env:"MONGO_ENDPOINTS" envSeparator:","`
	UserName  string   `toml:"username" env:"MONGO_USERNAME"`
	Password  string   `toml:"password" env:"MONGO_PASSWORD"`
	Database  string   `toml:"database" env:"MONGO_DATABASE"`
	AuthDB    string   `toml:"auth_db" env:"MONGO_AUTH_DB"`

	client *mongo.Client
	lock   sync.Mutex
}

func (m *mongodb) GetAuthDB() string {
	if m.AuthDB != "" {
		return m.AuthDB
	}

	return m.Database
}

func (m *mongodb) GetDB() (*mongo.Database, error) {
	conn, err := m.Client()
	if err != nil {
		return nil, err
	}
	return conn.Database(m.Database), nil
}

// 关闭数据库连接
func (m *mongodb) Close(ctx context.Context) error {
	if m.client == nil {
		return nil
	}

	return m.client.Disconnect(ctx)
}

// Client 获取一个全局的mongodb客户端连接
func (m *mongodb) Client() (*mongo.Client, error) {
	// 加载全局数据量单例
	m.lock.Lock()
	defer m.lock.Unlock()

	if m.client == nil {
		conn, err := m.getClient()
		if err != nil {
			return nil, err
		}
		m.client = conn
	}

	return m.client, nil
}

func (m *mongodb) getClient() (*mongo.Client, error) {
	opts := options.Client()

	if m.UserName != "" && m.Password != "" {
		cred := options.Credential{
			AuthSource: m.GetAuthDB(),
		}

		cred.Username = m.UserName
		cred.Password = m.Password
		cred.PasswordSet = true
		opts.SetAuth(cred)
	}
	opts.SetHosts(m.Endpoints)
	opts.SetConnectTimeout(5 * time.Second)
	// 添加MongoDB的trace
	opts.Monitor = otelmongo.NewMonitor(
		otelmongo.WithCommandAttributeDisabled(true),
	)

	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second*5))
	defer cancel()

	// Connect to MongoDB
	client, err := mongo.Connect(ctx, opts)
	if err != nil {
		return nil, fmt.Errorf("new mongodb client error, %s", err)
	}

	if err = client.Ping(ctx, nil); err != nil {
		return nil, fmt.Errorf("ping mongodb server(%s) error, %s", m.Endpoints, err)
	}

	return client, nil
}

func newJaeger() *jaeger {
	// 使用官方默认值
	return &jaeger{
		Endpoint: "http://localhost:14268/api/traces",
	}
}

type jaeger struct {
	Endpoint string `toml:"endpoint" json:"endpoint" yaml:"endpoint" env:"JAEGER_ENDPOINT"`
}
